#include <length.h>
#include <structure.h>
#include <MC.h>
#include <PAL/MMU.h>
#include <OSProc.h>
#include "MM.h"

using namespace PAL;

typedef struct SLIST<3> staticlist2;

#define _NORMAL_    (0)
#define _BUFFER_    (1<<0)
#define _LOCKED_    (1<<1)
#define _COW_       (1<<2)

#define CheckBufferMemory(pro)  _CheckMemory(pro,_BUFFER_)
#define MakeBufferMemory(pro)   _MakeMemory(pro,_BUFFER_)
#define CheckLockedMemory(pro)  _CheckMemory(pro,_LOCKED_)
#define MakeLockedMemory(pro)   _MakeMemory(pro,_LOCKED)
#define MakeUnlockedMemory(pro) _MakeMemory(pro,_NORMAL_)

#define _GetProcess(len)  len.data_[0]
#define _GetAddress(len)  len.data_[1]

#define PHYMEMNODE(OFFSET)  ((((_ptlong)(OFFSET)) - PHY_MEMORY_START) >> 4K)

#define _INSERT(__member,_contain,__MAKENODE__,__INSERTNODE__) \
  (_member == NULL						\
   ? (_member = __MAKENODE__(_KMalloc(sizeof(_member)),_contain),1) \
   : (__INSERTNODE__(_member,__MAKENODE__(_KMalloc(sizeof(_member)),_contain)),1)) 

#define _REMOVE(_member,_contain,__REMOVENODE__)	\
  ({auto fun = [&fun](decltype(_member) node)->int	\
  {							\
    (node == NULL					\
    ? 0							\
    : ( ((decltype(_contain))(node->mark)) == _contain	\
	? (__REMOVENODE__(_member,node),_KFree(node),1)	\
	: fun(node->next)				\
	)						\
     );							\
  };fun();})



  #define 4K  12
  static _ptlong* PHY_MEMORY_START;
  static _ptlong  PHY_MEMORY_SIZE; //*4K 
  volatile staticlist2 phyMemList;
  volatile _mlock globalMemLock = {NULL};
  typedef pair<_pcb*,_ptlong> _proaddr;
  typedef map<_proaddr,set<_proaddr>> _plist;

  _plist Proaddr([](_ptlong size)->_ptlong*{return _KMalloc(size);});
  //Warning: could be initialized like this?
  
  int MM_INIT()
  {
    MM_PhyMem_Init(&phyMemlist,PHY_MEMORY_SIZE);
    MUTEX_Init(&globalMemLock);
  }
  /*
    This functions is called by 'MemoryAbortSVR'. It's mainly responsible for Poping out the most spare PhyMem, Save it only after checking 
    it's have been used and it has been connected between Vaddr and Paddr(?) and it's not a buffer memory.
    After that if 'RecoverMemory' is given, it will call it.(Give or not is decided by 'MemoryAbortSVR' checking the invalid page discription
    In the end, a new connection between Vaddr and Paddr will be built. 
    The function is run in no-auto-scheduling mode.

    PS: A page discription has only three mode: normal, unallocated, swapped out, copy-on-write(read only in kernel)
    PPS: it is used to solve COW Abort
   */
  //  _ptlong ActivateMemory(_pcb* pro,unsigned int* vMem,int (*SaveMemory)(unsigned int*/*phyMem*/,_length/*offset to get the path for SWAP*/),
  //		     int (*RecoverMemory)(unsigned int*/*phyMem*/,_length),int (*CheckVirMemory(_pcb*/*processID*/,unsigned int*/*virMem*/)))
  //CheckVirMemory should be checking 
  _ptlong ActivateMemory(_pcb* pro,_ptlong* vMem,
			 (int (*memoryswap)(_pcb*, //swapout
					    _ptlong*, //swapout addr
					    _pcb*, //swapin
					    _ptlong*, //swapin addr
					    _ptlong*, //phymem
					    int (*)(
				_pcb*/*swapin*/,_ptlong*/*swapin*/,
				int(*/*SaveMemory*/)(_ptlong*,_length),
				int(*/*RecoverMemory*/)(_ptlong*,_length),
				int(*/*CheckMemory*/)(_ptlong*,_length)
						    )))
  {
    //Some problem. Because when it's running,the mutex_lock maybe locked.
    //We need a new special lock.
    //Stop Auto threading
    //Use backup rings.
    ring _backring;
    ring* backring = RingInit(&_backring,run->mark);
    ring* nowring = run;
    run = backring;
    ring* others = MUTEX_SPLOCK(&globalMemLock);
    RingCombine(nowring,others);
    

    _ptlong* phymem = PopEmptyMemory(phyMemList);

    //    LockMemory(pro,phymem);  //ReLock & Unlock
    HaveVisitedMemory(phyMem);
    
    MUTEX_UNLOCK(&globalMemLock);
    _length meminfo = GetMemInfo(&phyMemList,phymem);
    _length newmeminfo = Makelength(pro,vMem);
    memoryswap(_GetProcess(meminfo),_GetAddress(meminfo),pro,vMem,phymem,
	       [](_pcb* pro,_ptlong* vMem,
		  int(*SaveMemory)(_ptlong*,_length),
		  int(*RecoverMemory)(_ptlong*,_length),
		  int(*CheckMemory)(_ptlong*,_length))->int
	       {
		 using namespace MM;
		 _length meminfo = {0};
		 _length newmeminfo = Makelength(pro,vMem);
		 if(SaveMemory != NULL 
		    && lengthCompare(meminfo = GetMemInfo(&phyMemList,phymem),Makelength<0>()) != 0 //Check PhyMem Used
		    && CheckVirMemory(meminfo.data_[0],meminfo.data_[1]) != 0 //Check VirMem Valid
		    && !CheckBufferMemory(GetMemFeature(&phyMemList,phyMem))) //BufferMemory doesn't need to restore
		   {
		     SaveMemory(phymem,meminfo);
		   }
		 if(RecoverMemory != NULL)
		   {
		     RecoverMemory(phymem,newmeminfo);
		   }
		 return 1;
		 // UnlockMemory(pro,phymem);
	       });
    SetMemInfo(&phyMemList,phymem,newmeminfo);
    MMUMemoryMap(pro->flPage,phymem,vMem);
    run = nowring;
    return phymem;
    
  }

 
  inline int HaveVisitedMemory(_pcb* pro,_ptlong* vMem)  //argv is not certain: if we can directly get the phyMem?
  {
    //    unsigned int* pMem = MMUGetPMem(flpage,vMem);
    HaveVisitedMemory(MMUGetPMem(pro,vMem));
  }
  inline int HaveVisitedMemory(_ptlong phyMem)
  {
    MUTEX_LOCK(&globalMemLock);
    UpStaticListNode(&phyMemList,&(phyMemList.content[PHYMEMNODE(phyMem)]));
    MUTEX_UNLOCK(&globalMemLock);
  }

  inline void PushAbandenedMemory(_ptlong* phyMem)
  {//cancel the mapping,turn to inallocated mode,Release phyMem & put it back to the tail of the queue 
    MUTEX_LOCK(&globalMemLock);
    _length info = GetMemInfo(&phyMemList,phyMem);
    MMUMemorySet(_GetProcess(info),_GetAddress(info),_UNALLOCATED);
    SetMemInfo(&phyMemList,phyMem,Makelength<0>());
    DownStaticListNode(&phyMemList,&(phyMemList.content[PHYMEMNODE(phyMem)]));
    MUTEX_UNLOCK(&globalMemLock);
    
  }
  inline void PushBufferMemory(_ptlong* phyMem)
  { 
    MUTEX_LOCK(&globalMemLock);
    SetMemFeature(&phyMemList,phyMem,_BUFFER_);
    MUTEX_UNLOCK(&globalMemLock);
  }

  template<typename T>
  inline int CheckBufferMemoryValidThen(_pcb* pro,_ptlong* vMem,T fun)
  {
    MUTEX_LOCK(&globalMemLock);
    _ptlong ret = LockMemory(pro,vMem);
    _ptlong buffercheck = (Comparelength(GetMemInfo(&phyMemList,MMUGetPMem(pro,vMem)), Makelength(pro,vMem)));
    MUTEX_UNLOCK(&globalMemLock);
    if(ret != 0)
      {
	if(buffercheck == 0)
	  fun();

	ret = UnlockMemory(pro,vMem);
      }
    return ret;
  }

  inline int LockMemory(_pcb* pro,_ptlong* vMem)
  {
    MUTEX_LOCK(&globalMemLock);
    _ptlong phyMem = MMUGetPMem(pro->flPage,vMem);
    //Check Permission
    _ptlong ret = 0;
    // _ptlong feature ;
    if(!CheckLockedMemory(GetMemFeature(&phyMemList,phyMem)))
      {
	ret =  (phyMem == NULL
			? 0
			: SetMemFeature(&phyMemList,phyMem, _LOCK_));
	_INSERT(pro->lockedmemory,phyMem,MakeSListNode,InsertSListNode);
      }
    MUTEX_UNLOCK(&globalMemLock);
    return ret;

  }
  inline int UnlockMemory(_pcb* pro,_ptlong* vMem)
  {
    MUTEX_LOCK(&globalMemLock);
    _ptlong phyMem = MMUGetPMem(pro->flPage,vMem);
    //Check Permission
    _ptlong ret = 0;
    if(ret = _REMOVE(pro->lockedmemory,phyMem,RemoveSListNode) == 1)
      {
	ret = RemoveMemFeature(&phyMemList,phyMem,_LOCK_);
      }
    MUTEX_UNLOCK(&globalMemLock);
    return ret;
    
  }
  inline int CopyOnWrite(_pcb* pro,_ptlong*vMem,_ptlong* phyAddr)
  {
    _length info = GetMemInfo(phyMemList,phyAddr);
    return CopyOnWrite(pro,vMem,_GetProcess(info),_GetAddress(info));
  }
  inline int CopyOnWrite(_pcb* proA,_ptlong* vMemA,_pcb* proB,_ptlong* vMemB)
  {//proA map to proB , proB is the mapped one
    _ptlong* phyAddr = GetPMem(proB->flPage,vMemB); 
    if(phyAddr == NULL)
      {//SwapOut or Unallocated
	
      }
       
    Proaddr::iterator it;
    set<_proaddr>* tset = ((it = Proaddr.find(_proaddr(proB,vMemB))) == Proaddr.end()
			   ?(tset = _KMalloc(sizeof(set<_proaddr>))
			     ,tset->set<_proaddr>([](_ptlong size)->_ptlong*{return (_ptlong*)_KMalloc(size);})
			     ,tset->insert(_proaddr(proB,vMemB))
			     ,tset)
			   :(*it));
    tset.insert(_proaddr(proA,vMemA));
    SetMemFeature(phyMemList,phyAddr,_COW_);
    MMUMemorySet(proB->flPage,vMemB,_COPY_ON_WRITE);
    MMUMemoryMap(proA->flPage,phyAddr,vMemA,_COPY_ON_WRITE);
    
  }


  inline _ptlong CopyOnWrite_Swapout(_pcb* pro,_ptlong* vMem)
  {
    Proaddr::iterator it;
    set<_proaddr>* tset   = ((it = Proaddr.find(_proaddr(pro,vMem)) == Proaddr.end())
			     ? NULL
			     : (*it));
    if(tset == NULL)
      return NULL;
    
    for(set<_proaddr>::iterator paddrit = tset->begin();paddrit != tset->end(); paddrit ++)
      {
	MMUMemorySet((*paddrit).a,(*paddrit).b,PAGE_SWAP_OUT);
      }
    return 1;

  }
  
  inline _ptlong CopyOnWrite_Swapin(_pcb* pro,_ptlong* vMem,_ptlong* phyAddr)
  {
    Proaddr::iterator it;
    set<_proaddr>* tset   = ((it = Proaddr.find(_proaddr(pro,vMem)) == Proaddr.end())
			     ? NULL
			     : (*it));
    if(tset == NULL)
      return NULL;
    
    for(set<_proaddr>::iterator paddrit = tset->begin();paddrit != tset->end(); paddrit ++)
      {
	MMUMemoryRemove((*paddrit).a,(*paddrit).b,PAGE_SWAP_OUT);
	MMUMemoryMap((*paddrit).a,phyAddr,(*paddrit).b);
      }
    return 1;
    
  }
  /*
    
   */
  /*
  inline int CopyOnWrite_Event(_pcb* pro,_ptlong* vMem,_ptlong* phyAddr)
  {
    //vMem is being written.
    
    }*/
  //CopyOnWrite_Event is included in ActivateMemory
  //By ergodic we find the phyMem of COWs




static inline staticlist* MM_PhyMem_Init(staticlist2* list,const unsigned int phymemSize)
{
  return InitializeStaticList(list,phymemSize,_KMALLOC);
}

static  inline _ptlong* PopEmptyMemory(staticlist2* list)
{//rewrite: skip the locked memory & Release the buffer memory: cancel the mapping, turn to inallocated mode & return it
  //if we don't swap out _COW_, forks of process will make kernel dead.
  typename staticlist2::Ntype* node = GetLastListNode(list);
  while( node->mark[2] & (_LOCKED_ | _COW_))
    {
      node = node->pre;
    }
  
  return (unsigned int*)
    ((((unsigned long)(((_ptlong)(node)) - (_ptlong)(list->content))) / sizeof(typename decltype(list)::Ntype)) + ((unsigned long)(MM::PHY_MEMORY_START)));
}


static inline _length GetMemInfo(staticlist2* list,unsigned int* phyMem)
{
  _length ret;
  _Memcpy(&(list->content [PHYMEMNODE(phyMem)].mark), 
	  &ret,
	  sizeof(_length));

  return ret;
  
}

static inline _ptlong GetMemFeature(staticlist2* list,_ptlong* phyMem)
{
   return list->content[PHYMEMNODE(phyMem)].mark[2]; 
}

static inline int SetMemInfo(staticlist2* list,_ptlong* phyMem,const _length& MARK)
{

  _Memcpy(&MARK,
	  &(list->content [PHYMEMNODE(phyMem)].mark),
	  sizeof(_length));

  return 1;
}
 static inline _ptlong SetMemFeature(staticlist2* list,_ptlong phyMem, _ptlong MARK)
 {
   list->content [ ((((unsigned long)phyMem) - ((unsigned long)(PHY_MEMORY_START))) >> 4K)].mark[2] |= MARK;
   return 1;
 }

  static inline _ptlong RemoveMemFeature(staticlist2* list,_ptlong phyMem, _ptlong MARK)
  {
    list->content [ ((((unsigned long)phyMem) - ((unsigned long)(PHY_MEMORY_START))) >> 4K)].mark[2] &= (~MARK);
    return 1;

  }
